package net.sf.odinms.exttools.doclet.pages;

import java.io.IOException;
import java.util.*;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.ProgramElementDoc;
import com.sun.tools.doclets.internal.toolkit.util.ClassUseMapper;
import com.sun.tools.doclets.internal.toolkit.util.DirectoryManager;
import net.sf.odinms.exttools.doclet.AbstractPageWriter;
import net.sf.odinms.exttools.doclet.Doclet;

/**
 * Creates "{packagepath}/class-use/{MemberName}.html" detailing class use.
 */
@SuppressWarnings("unchecked")
public final class ClassUse extends AbstractPageWriter {
	
	/** The class object for which usage is being detailed. */
	private ClassDoc theClass;
	/** Reference to the class which precedes this one. (alpha-ordered) */
	private ClassDoc prev;
	/** Reference to the class which follows this one. (alpha-ordered) */
	private ClassDoc next;
	
	private SortedSet<PackageDoc> pkgSet;
	
	private Set<PackageDoc> pkgsWithPackageAnnotations = null;
	
	private final Map<String, List<ProgramElementDoc>> pkgsWithClassAnnotations;
	private final Map<String, List<ProgramElementDoc>> pkgsWithClassTypeParameters;
	private final Map<String, List<ProgramElementDoc>> pkgsWithSubclasses;
	private final Map<String, List<ProgramElementDoc>> pkgsWithSubinterfaces;
	private final Map<String, List<ProgramElementDoc>> pkgsWithImplementingClasses;
	
	private final Map<String, List<ProgramElementDoc>> pkgsWithField;
	private final Map<String, List<ProgramElementDoc>> pkgsWithFieldAnnotations;
	private final Map<String, List<ProgramElementDoc>> pkgsWithFieldTypeParameters;
	
	private final Map<String, List<ProgramElementDoc>> pkgsWithConstructorAnnotations;
	private final Map<String, List<ProgramElementDoc>> pkgsWithConstructorParameterAnnotations;
	private final Map<String, List<ProgramElementDoc>> pkgsWithConstructorThrows;
	private final Map<String, List<ProgramElementDoc>> pkgsWithConstructorArgs;
	private final Map<String, List<ProgramElementDoc>> pkgsWithConstructorArgsTypeParameters;
	
	private final Map<String, List<ProgramElementDoc>> pkgsWithMethodAnnotations;
	private final Map<String, List<ProgramElementDoc>> pkgsWithMethodParameterAnnotations;
	private final Map<String, List<ProgramElementDoc>> pkgsWithMethodTypeParameters;
	private final Map<String, List<ProgramElementDoc>> pkgsWithMethodThrows;
	private final Map<String, List<ProgramElementDoc>> pkgsWithMethodArgs;
	private final Map<String, List<ProgramElementDoc>> pkgsWithMethodArgsTypeParameters;
	private final Map<String, List<ProgramElementDoc>> pkgsWithMethodReturn;
	private final Map<String, List<ProgramElementDoc>> pkgsWithMethodReturnTypeParameters;
	
	private ClassUseMapper mapper = CONF.classUseMapper;

	/**
	 * Generate page detailing usage of the specified class.
	 * 
	 * @param classDoc Class for which to document usage throughout the project.
	 * @param prevClass The preceding class in the alpha-sorted list.
	 * @param nextClass The following class in the alpha-sorted list.
	 */
	public static void generateClassUse(ClassDoc classDoc, ClassDoc prevClass,
			ClassDoc nextClass) {
		String filename = classDoc.name() + CONF.ext;
		String path =
				DirectoryManager.getDirectoryPath(classDoc.containingPackage())
						+ "class-use/";
		try {
			new ClassUse(filename, path, classDoc, prevClass, nextClass);
		}
		catch (IOException exc) {
			throw Doclet.exception(exc, filename);
		}
	}

	/**
	 * Initialize and create class usage page for the specified class.
	 * 
	 * @param filename The desired name of the file (with extension).
	 * @param pathToFile The path from the document root to this file.
	 * @param theClass Class for which to document usage throughout the project.
	 * @param prevClass The preceding class in the alpha-sorted list.
	 * @param nextClass The following class in the alpha-sorted list.
	 * @throws IOException If {@link java.io.FileOutputStream} creation fails.
	 */
	private ClassUse(String filename, String pathToFile, ClassDoc theClass,
			ClassDoc prevClass, ClassDoc nextClass) throws IOException {
		super(filename, pathToFile);
		pageType = PageType.CLASS_USE;
		CONF.currentPkg = theClass.containingPackage();
		this.theClass = theClass;
		this.prev = prevClass;
		this.next = nextClass;
		
		pkgSet = new TreeSet<PackageDoc>();
		pkgsWithClassTypeParameters =
				extractClassUse(mapper.classToClassTypeParam);
		pkgsWithClassAnnotations =
				extractClassUse(mapper.classToClassAnnotations);
		pkgsWithMethodTypeParameters =
				extractClassUse(mapper.classToExecMemberDocTypeParam);
		pkgsWithMethodArgsTypeParameters =
				extractClassUse(mapper.classToExecMemberDocArgTypeParam);
		pkgsWithFieldTypeParameters =
				extractClassUse(mapper.classToFieldDocTypeParam);
		pkgsWithFieldAnnotations = extractClassUse(mapper.annotationToFieldDoc);
		pkgsWithMethodReturnTypeParameters =
				extractClassUse(mapper.classToExecMemberDocReturnTypeParam);
		pkgsWithMethodAnnotations =
				extractClassUse(mapper.classToExecMemberDocAnnotations);
		pkgsWithMethodParameterAnnotations =
				extractClassUse(mapper.classToExecMemberDocParamAnnotation);
		pkgsWithSubclasses = extractClassUse(mapper.classToSubclass);
		pkgsWithSubinterfaces = extractClassUse(mapper.classToSubinterface);
		pkgsWithImplementingClasses =
				extractClassUse(mapper.classToImplementingClass);
		pkgsWithField = extractClassUse(mapper.classToField);
		pkgsWithMethodReturn = extractClassUse(mapper.classToMethodReturn);
		pkgsWithMethodArgs = extractClassUse(mapper.classToMethodArgs);
		pkgsWithMethodThrows = extractClassUse(mapper.classToMethodThrows);
		pkgsWithConstructorAnnotations =
				extractClassUse(mapper.classToConstructorAnnotations);
		pkgsWithConstructorParameterAnnotations =
				extractClassUse(mapper.classToConstructorParamAnnotation);
		pkgsWithConstructorArgs =
				extractClassUse(mapper.classToConstructorArgs);
		pkgsWithConstructorArgsTypeParameters =
				extractClassUse(mapper.classToConstructorDocArgTypeParam);
		pkgsWithConstructorThrows =
				extractClassUse(mapper.classToConstructorThrows);
		
		windowTitle = theClass.name();
		printXhtmlHeader();
		
		printEntityCaptionPackage(theClass, CONF.propertyText("use.Usage_of_0",
				classFlavor(theClass, true)),
				theClass.containingPackage().name());
		
		if (pkgSet.size() <= 0) {
			println(open("p")
					+ CONF.propertyText("use.No_usage_of_0",
							theClass.qualifiedName() + close("p")));
		} else {
			if (CONF.packages.length > 1) {
				println(openDivWithID("Summaries"));
				generatePackageList();
				generatePackageAnnotationList();
				println(close("div") + getComment("Summaries"));
			}
			println(openDivWithID("Details"));
			generateClassList();
			println(close("div") + getComment("Details"));
		}
		
		printXhtmlFooter();
		this.close();
	}
	
	/** Print section for each using package, and use within the package. */
	private void generatePackageList() {
		// Print header with link to matching class summary
		println(open("h2")
				+ CONF.propertyText(
						"use.Packages_that_use_0",
						new TagBuilder("a").add("href",
								"../" + theClass.name() + CONF.ext).getOpenTextClose(
								theClass.qualifiedName())) + close("h2"));
		String pkgName;
		println(open("table"));
		for (PackageDoc pkg : pkgSet) {
			pkgName = pkg.name();
			println(open("tr"));
			println(open("td")
					+ new TagBuilder("a").add("href", "#" + pkgName).getOpenTextClose(
							pkgName) + close("td"));
			println(open("td") + getCommentSummary(pkg) + close("td"));
			println(close("tr"));
		}
		println(close("table"));
	}
	
	/** Print sections for "Packages with annotations of type __". */
	private void generatePackageAnnotationList() {
		if ((!theClass.isAnnotationType())
				|| pkgsWithPackageAnnotations == null
				|| pkgsWithPackageAnnotations.size() == 0)
			return;
		
		println(open("h2")
				+ CONF.propertyText("use.PackageAnnotation",
						theClass.qualifiedName()) + close("h2"));
		
		for (PackageDoc pkg : pkgsWithPackageAnnotations) {
			System.out.println("****");
			println(pkg.name() + "<br />");
		}

	}
	
	/** Iterate through a using package and print usage by all its members. */
	private void generateClassList() {
		for (Iterator it = pkgSet.iterator(); it.hasNext();) {
			PackageDoc pkg = (PackageDoc) it.next();
			// FIXME Clicking to package summary breaks history (JavaScript)
			println(open("h2 id=\"" + pkg.name() + "\"")
					+ CONF.propertyText("use.Uses_in_0", linkToLabelHref(
							pkg.name(), "../" + hrefToDoc(pkg)
									+ "package-summary" + CONF.ext))
					+ close("h2"));

			generateClassUse(pkg);
		}
	}
	
	/**
	 * Extract usage (by package) of this class by elements of a given type.
	 * 
	 * @param usageMap A mapping of classes to program elements that use them.
	 * @return A map of using packages to their elements that use this class.
	 */
	@SuppressWarnings("unchecked")
	private Map<String, List<ProgramElementDoc>> extractClassUse(Map usageMap) {
		// Get a list of items whose usage map to this class.
		List list = (List) usageMap.get(theClass.qualifiedName());
		if (list != null) {
			Map<String, List<ProgramElementDoc>> usingPackageMap =
					new HashMap<String, List<ProgramElementDoc>>();
			Collections.sort(list);
			// Process items that use this class, dividing in bins by package.
			Iterator it = list.iterator();
			while (it.hasNext()) {
				ProgramElementDoc doc = (ProgramElementDoc) it.next();
				PackageDoc pkg = doc.containingPackage();
				pkgSet.add(pkg);
				// Get the current list of ProgramElementDoc objects in a given
				// package which use the current class.
				List<ProgramElementDoc> inPkg = usingPackageMap.get(pkg.name());
				if (inPkg == null) {
					// If no list exists, create one and add <key,value> to map.
					inPkg = new ArrayList<ProgramElementDoc>();
					usingPackageMap.put(pkg.name(), inPkg);
				}
				inPkg.add(doc);
			}
			return usingPackageMap;
		} else
			return new HashMap<String, List<ProgramElementDoc>>();
	}
	
	/**
	 * Print the specific ways the class is used within a given package.
	 * 
	 * @param pkg The package for which to print use of this class.
	 */
	private void generateClassUse(PackageDoc pkg) {
		String pkgName = pkg.name();
		String pkgLink = linkToLabelHref(escapeHTML(pkgName), hrefToDoc(pkg));
		String classLink =
				linkToLabelHref(escapeHTML(this.theClass.name()),
						hrefToDoc(this.theClass));
		
		this.printUseInfo(pkgsWithClassAnnotations.get(pkgName),
				CONF.propertyText("use.ClassAnnotations", classLink, pkgLink));
	
		this.printUseInfo(
				pkgsWithClassTypeParameters.get(pkgName),
				CONF.propertyText("use.ClassTypeParameters", classLink, pkgLink));

		this.printUseInfo(pkgsWithSubclasses.get(pkgName), CONF.propertyText(
				"use.Subclasses", classLink, pkgLink));

		this.printUseInfo(pkgsWithSubinterfaces.get(pkgName),
				CONF.propertyText("use.Subinterfaces", classLink, pkgLink));

		this.printUseInfo(
				pkgsWithImplementingClasses.get(pkgName),
				CONF.propertyText("use.ImplementingClasses", classLink, pkgLink));

		this.printUseInfo(pkgsWithField.get(pkgName), CONF.propertyText(
				"use.Field", classLink, pkgLink));

		this.printUseInfo(pkgsWithFieldAnnotations.get(pkgName),
				CONF.propertyText("use.FieldAnnotations", classLink, pkgLink));

		this.printUseInfo(
				pkgsWithFieldTypeParameters.get(pkgName),
				CONF.propertyText("use.FieldTypeParameters", classLink, pkgLink));

		this.printUseInfo(pkgsWithConstructorAnnotations.get(pkgName),
				CONF.propertyText("use.ConstructorAnnotations", classLink,
						pkgLink));

		this.printUseInfo(pkgsWithConstructorParameterAnnotations.get(pkgName),
				CONF.propertyText("use.ConstructorParameterAnnotations",
						classLink, pkgLink));

		this.printUseInfo(pkgsWithConstructorThrows.get(pkgName),
				CONF.propertyText("use.ConstructorThrows", classLink, pkgLink));
		

		this.printUseInfo(pkgsWithConstructorArgs.get(pkgName),
				CONF.propertyText("use.ConstructorArgs", classLink, pkgLink));

		this.printUseInfo(pkgsWithConstructorArgsTypeParameters.get(pkgName),
				CONF.propertyText("use.ConstructorArgsTypeParameters", classLink, pkgLink));
		
		this.printUseInfo(pkgsWithMethodAnnotations.get(pkgName),
				CONF.propertyText("use.MethodAnnotations", classLink, pkgLink));
		
		this.printUseInfo(pkgsWithMethodParameterAnnotations.get(pkgName),
				CONF.propertyText("use.MethodParameterAnnotations", classLink, pkgLink));
		
		this.printUseInfo(pkgsWithMethodTypeParameters.get(pkgName),
				CONF.propertyText("use.MethodTypeParameters", classLink, pkgLink));
		
		this.printUseInfo(pkgsWithMethodThrows.get(pkgName),
				CONF.propertyText("use.MethodThrows", classLink, pkgLink));
		
		this.printUseInfo(pkgsWithMethodArgs.get(pkgName),
				CONF.propertyText("use.MethodArgs", classLink, pkgLink));

		this.printUseInfo(pkgsWithMethodArgsTypeParameters.get(pkgName),
				CONF.propertyText("use.MethodArgsTypeParameters", classLink, pkgLink));
		
		this.printUseInfo(pkgsWithMethodReturn.get(pkgName),
				CONF.propertyText("use.MethodReturn", classLink, pkgLink));

		this.printUseInfo(pkgsWithMethodReturnTypeParameters.get(pkgName),
				CONF.propertyText("use.MethodReturnTypeParameter", classLink, pkgLink));
	}
	
	private void printUseInfo(List<ProgramElementDoc> mems, String heading) {
		if (mems == null) {
			return;
		}
		
		if (mems.size() > 0) {
			println(open("h3") + heading + close("h3"));
			println(open("table"));
			
			for (ProgramElementDoc mem : mems) {
				String memName = mem.name();
				String memLink;
				
				if (mem instanceof ClassDoc) {
					memLink =
							linkToLabelHref(escapeHTML(memName), hrefToDoc(mem));
				} else {
					memLink =
							linkToLabelHref(escapeHTML(memName),
									hrefToDoc(mem.containingClass()) + "#"
											+ escapeHTML(memName));
				}
				println(open("tr"));
				println(open("td") + memLink + close("td"));
				println(open("td") + getCommentSummary(mem) + close("td"));
				println(close("tr"));
			}
			
			println(close("table"));
		}
	}
	
	/** Link "Package" to the enclosing package's summary. */
	// TODO Verify behavior for 0 or 1 packages
	protected void navLinkPackage() {
		println(listItem(linkToLabelHrefTitle(PACKAGE, ".." + SLASH
				+ "package-summary" + CONF.ext, CONF.propertyText(
				"navtitle.Package", theClass.containingPackage().name()))));
	}
	
	/** Link "Class" to the current class. */
	protected void navLinkClass() {
		println(listItem(linkToLabelHrefTitle(CLASS, ".." + SLASH + filename,
				CONF.propertyText("navtitle.Class", theClass.name()))));
	}
	
	/** Highlight "Use" as current section, don't create link. */
	protected void navLinkUse() {
		println(listItemCurrent(USE));
	}
	
	/** Link "Tree" to the enclosing package's tree. */
	protected void navLinkTree() {
		println(listItem(linkToLabelHrefTitle(TREE, ".." + SLASH
				+ "package-tree" + CONF.ext, CONF.propertyText(
				"navtitle.Tree.package", theClass.containingPackage().name()))));
	}
	
	/** Print links to previous and next classes, or static label if none. */
	protected void navPreviousNext() {
		println(openULWithID("PreviousNext"));
		
		println(listItem(linkToLabelHref(PREV_CLASS, hrefToDoc(prev)))
				+ listItemLast(linkToLabelHref(NEXT_CLASS, hrefToDoc(next))));
		println(close("ul"));
	}
	
}
